﻿// HSS.Interlink.DownloadFileDialog.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       DownloadFileDialog.cs
// Author:     HSS\gbanta
// Created:    07/16/2011
// Modified:   07/22/2011
// ----------------------------------------------------------------------------
namespace HSS.Interlink
{
	#region Using Directives
	using System;
	using System.ComponentModel;
	using System.IO;
	using System.Net;
	using System.Windows;
	using System.Windows.Browser;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Media;
	using System.Windows.Media.Imaging;
	#endregion

	#region DownloadFileDialog
	/// <summary>
	/// DownloadFileDialog downloads the requested file(s) from the the Web Server and 
	/// shows the progress.
	/// </summary>
	public sealed class DownloadFileDialog : ChildWindow, INotifyPropertyChanged
	{
		#region Events
		/// <summary>
		/// Event fired when the download processing has completed.
		/// </summary>
		public event EventHandler<DownloadCompletedEventArgs> Completed;
		private void OnCompleted(bool canceled, Exception ex)
		{
			var handler = Completed;
			if (null != handler)
				handler(this, new DownloadCompletedEventArgs(canceled, ex));
		}
		#endregion

		#region Constants
		/// <summary>
		/// The default WebConfig ApplicationSetting Key (Default: 'DownloadHandler') that contains the Handler for the file download.
		/// </summary>
		public const string DefaultHandlerKey = "DownloadHandler";
		/// <summary>
		/// The default DownloadUri (Default: '/FileDownload.ashx') that matches the Handler Path in the Web.Config
		/// </summary>
		public const string DefaultDownloadUri = "/FileDownload.ashx";
		/// <summary>
		/// The default Dialog Title (HSS Interlink: File Download).
		/// </summary>
		public const string DefaultTitle = "HSS Interlink: File Download";

		const byte bkfocusAlpha = 207;
		#endregion

		#region Fields
		Border mainBackground;
		TextBlock txtResult;
		ButtonBase btnClose;
		CheckBox ckbCloseWhenCompleted;
		Stopwatch watch;
		WebClient webClient;
		Guid jobId;
		Uri resolvedUri;
		string metadata;
		bool mulitpleFiles;
		bool isCompleted;
		bool isCanceled;
		bool hidden = false;
		#endregion

		#region Constructor
		private DownloadFileDialog(string saveAsFileName, Stream stream, bool isHidden, params string[] fileNames)
		{
			this.hidden = isHidden;

			this.DefaultStyleKey = typeof(DownloadFileDialog);
			this.Title = DownloadFileDialog.DefaultTitle;

			if (string.IsNullOrEmpty(saveAsFileName))
				throw new ArgumentNullException("saveAsFileName");

			if (null == stream)
				throw new ArgumentNullException("stream");

			if (null == fileNames)
				throw new ArgumentNullException("fileNames");

			if (fileNames.Length == 0)
				throw new ArgumentOutOfRangeException("fileNames");

			this.jobId = Guid.NewGuid();
			this.HandlerKey = DownloadFileDialog.DefaultHandlerKey;
			this.mulitpleFiles = fileNames.Length > 1;
			this.SourceFiles = fileNames;
			this.metadata = string.Join(",", fileNames);
			this._stream = stream;
			this.SaveAsFileName = saveAsFileName;

			if (!this.hidden)
			{
				this.DataContext = this;
				this.OnApplyTemplate();

				this.Loaded += (s, e) =>
				{
					try
					{
						GetDownloadUrl(this.DownloadUri, out resolvedUri);
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.Message);
						OnCompleted(true, ex);
						this.Close();
						return;
					}
					this.Closing += (me, args) =>
					{
						if (!this.isCompleted)
						{
							args.Cancel = true;
							this.DownloadCanceled(null);
						}
					};
					this.PostRequest();
				};
			}
		}
		/// <summary>
		/// Creates a new instance of the Download File Dialog and when the Dialog is shown
		/// begins downloading the requested file(s), writing the downloaded file to the 
		/// provided stream.
		/// </summary>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="fileNames">The array of Names or Ids or Keys of the files to be download.</param>
		/// <exception cref="ArgumentNullException">saveAsFileName is missing or empty.</exception>
		/// <exception cref="ArgumentNullException">The stream object is null.</exception>
		/// <exception cref="ArgumentNullException">The array of file names is null.</exception>
		/// <exception cref="ArgumentOutOfRangeException">The array of file names is empty.</exception>
		public DownloadFileDialog(string saveAsFileName, Stream stream, params string[] fileNames)
			: this(saveAsFileName, stream, false, fileNames)
		{
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets the formatted Title.
		/// </summary>
		public string FormattedTitle
		{
			get { return "Download: " + SaveAsFileName; }
		}
		/// <summary>
		/// Gets the name of the file the downloaded contents will be saved to.
		/// </summary>
		public string SaveAsFileName
		{
			get { return _saveAsFileName; }
			private set
			{
				_saveAsFileName = value;
				this.OnPropertyChanged("SaveAsFileName");
			}
		} string _saveAsFileName;
		/// <summary>
		/// Gets the array of file names to be downloaded.
		/// </summary>
		public string[] SourceFiles
		{
			get
			{
				return _sourceFiles;
			}
			private set
			{
				_sourceFiles = value;
				this.OnPropertyChanged("SourceFiles");
			}
		} string[] _sourceFiles;
		/// <summary>
		/// Gets or sets the percentage of the bytes downloaded thus far.
		/// </summary>
		public int Percentage
		{
			get { return _percentage; }
			set
			{
				_percentage = value;
				this.OnPropertyChanged("Percentage");
				this.OnPropertyChanged("PercentageFormatted");
			}
		} int _percentage;
		/// <summary>
		/// Gets the Pecentage as a Formatted string.
		/// </summary>
		public string PercentageFormatted
		{
			get { return string.Format("{0} %", this.Percentage); }
		}
		/// <summary>
		/// Gets the current transfer rate
		/// </summary>
		public double TransferRate
		{
			get { return _transferRate; }
			private set
			{
				_transferRate = value;
				this.OnPropertyChanged("TransferRate");
				this.OnPropertyChanged("TransferRateFormatted");
			}
		} double _transferRate = 0.0;
		/// <summary>
		/// Gets the TransferRate as a formatted string.
		/// </summary>
		public string TransferRateFormatted
		{
			get
			{
				if (TransferRate < 1024.0)
					return string.Format("{0:N0} bytes/s", TransferRate);

				if (TransferRate < 1048576.0)
					return string.Format("{0:N0} KB/s", TransferRate / 1024.0);

				if (TransferRate < 1073741824.0)
					return string.Format("{0:N2} MB/s", TransferRate / 1048576.0);

				return string.Format("{0:N2} GB/s", TransferRate / 1073741824.0);
			}
		}
		/// <summary>
		/// Gets the elapsed time of the upload
		/// </summary>
		public TimeSpan ElapsedTime
		{
			get { return _elapsedTime; }
			private set
			{
				_elapsedTime = value;
				this.OnPropertyChanged("ElapsedTime");
			}
		} TimeSpan _elapsedTime;
		/// <summary>
		/// Gets a string showing the activity (download bytes so far in x amount of time).
		/// </summary>
		public string Activity
		{
			get
			{
				return _activity;
			}
			private set
			{
				_activity = value;
				this.OnPropertyChanged("Activity");
			}
		} string _activity;
		/// <summary>
		/// Gets or sets the WebConfig ApplicationSetting Key that contains the Handler for the 
		/// file download request.
		/// </summary>
		public string HandlerKey
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets the uri (absolute or relative) to the download http handler. For OOB must be the absolute Uri. Default: '/FileDownload.ashx'.
		/// </summary>
		public string DownloadUri
		{
			get
			{
				if (string.IsNullOrEmpty(_downloadUri))
					return DefaultDownloadUri;
				return _downloadUri;
			}
			set
			{
				if (string.IsNullOrEmpty(value))
					throw new NullReferenceException("Cannot set the download Uri to null or an empty string.");
				try
				{
					var uri = FileQuery.ResolveUrl(value);
					this._downloadUri = value;
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.Message, "Download", MessageBoxButton.OK);
					return;
				}
			}
		} string _downloadUri;
		/// <summary>
		/// Gets or sets whether or not to Close this Dialog when the Download Completes.
		/// </summary>
		public bool CloseWhenCompleted
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets whether or not to flush and close the stream. Default: True.
		/// </summary>
		public bool AutoCloseStream
		{
			get { return _autoCloseFileStream; }
			set { _autoCloseFileStream = value; }
		} bool _autoCloseFileStream = true;
		/// <summary>
		/// Gets or sets whether or not to set the streams position to zero once
		/// the download has completed. Requires that the stream is seekable and
		/// the <see cref="AutoCloseStream"/> be set to false. Default: True
		/// </summary>
		public bool AutoResetPosition
		{
			get { return _autoResetPosition; }
			set { _autoResetPosition = value; }
		} bool _autoResetPosition = true;
		/// <summary>
		/// Gets the stream containing the downloaded contents.
		/// </summary>
		public Stream Stream
		{
			get { return _stream; }
		} Stream _stream;

		#region TitleImage
		/// <summary>
		/// Gets or sets if the Title Image is visible.
		/// </summary>
		public bool IsTitleImageVisible
		{
			get { return _isTitleImageVisible; }
			set
			{
				_isTitleImageVisible = value;
				if (null != this.TitleImage)
				{
					if (!_isTitleImageVisible)
						this.TitleImage.Visibility = System.Windows.Visibility.Collapsed;
					else
						this.TitleImage.Visibility = System.Windows.Visibility.Visible;
				}
			}
		} bool _isTitleImageVisible = true;
		/// <summary>
		/// Gets the TitleImage.
		/// </summary>
		public Image TitleImage
		{
			get
			{
				if (null == _titleImage)
					_titleImage = this.GetTemplateChild("TitleImage") as Image;
				return _titleImage;
			}
		} Image _titleImage;
		/// <summary>
		/// Gets the default Image Source for the TitleImage.
		/// </summary>
		public BitmapImage TitleImageSource
		{
			get { return (BitmapImage)GetValue(TitleImageSourceProperty); }
			set { SetValue(TitleImageSourceProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for TitleImageSource.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty TitleImageSourceProperty =
			DependencyProperty.Register("TitleImageSource", typeof(BitmapImage), typeof(DownloadFileDialog), new PropertyMetadata(ResourceMgr.InterlinkIcon));
		#endregion

		#region DownloadImage

		/// <summary>
		/// Gets the DownloadImage.
		/// </summary>
		public Image DownloadImage
		{
			get
			{
				if (null == _downloadImage)
					_downloadImage = this.GetTemplateChild("DownloadImage") as Image;
				return _downloadImage;
			}
		} Image _downloadImage;
		/// <summary>
		/// Gets the default Image Source for the DownloadImage.
		/// </summary>
		public BitmapImage DownloadImageSource
		{
			get { return (BitmapImage)GetValue(DownloadImageSourceProperty); }
			set { SetValue(DownloadImageSourceProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for DownloadImageSource.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty DownloadImageSourceProperty =
			DependencyProperty.Register("DownloadImageSource", typeof(BitmapImage), typeof(DownloadFileDialog), new PropertyMetadata(ResourceMgr.GlobeImage));
		#endregion

		#region Background
		/// <summary>
		/// Gets or sets the Dialog Background
		/// </summary>
		public new SolidColorBrush Background
		{
			get
			{
				return (SolidColorBrush)base.GetValue(BackgroundProperty);
			}
			set
			{
				base.SetValue(BackgroundProperty, (DependencyObject)value);
				if (null != mainBackground && null != this.Background)
				{
					RadialGradientBrush bk = mainBackground.Background as RadialGradientBrush;
					bk.GradientStops[0].Color = Color.FromArgb(bkfocusAlpha, this.Background.Color.R, this.Background.Color.G, this.Background.Color.B);
				}
			}
		}
		#endregion

		#endregion

		#region Overrides
		/// <summary>
		/// Apply the Template
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			if (null != btnClose)
				btnClose.Click -= new RoutedEventHandler(btnClose_Click);
			btnClose = this.GetTemplateChild("btnClose") as ButtonBase;
			if (null != btnClose)
				btnClose.Click += new RoutedEventHandler(btnClose_Click);

			if (null != ckbCloseWhenCompleted)
			{
				ckbCloseWhenCompleted.Checked -= new RoutedEventHandler(ckbCloseWhenCompleted_Checked);
				ckbCloseWhenCompleted.Unchecked -= new RoutedEventHandler(ckbCloseWhenCompleted_Unchecked);
			}
			ckbCloseWhenCompleted = this.GetTemplateChild("ckbCloseWhenCompleted") as CheckBox;
			if (null != ckbCloseWhenCompleted)
			{
				ckbCloseWhenCompleted.IsChecked = this.CloseWhenCompleted;
				ckbCloseWhenCompleted.Checked += new RoutedEventHandler(ckbCloseWhenCompleted_Checked);
				ckbCloseWhenCompleted.Unchecked += new RoutedEventHandler(ckbCloseWhenCompleted_Unchecked);
			}

			txtResult = this.GetTemplateChild("txtResult") as TextBlock;

			if (null != this.TitleImage)
			{
				if (!IsTitleImageVisible)
					this.TitleImage.Visibility = System.Windows.Visibility.Collapsed;
				else
					this.TitleImage.Visibility = System.Windows.Visibility.Visible;
			}

			mainBackground = this.GetTemplateChild("MainBackground") as Border;
			if (null != mainBackground && null != this.Background)
			{
				RadialGradientBrush bk = mainBackground.Background as RadialGradientBrush;
				bk.GradientStops[0].Color = Color.FromArgb(bkfocusAlpha, this.Background.Color.R, this.Background.Color.G, this.Background.Color.B);
			}
		}
		#endregion

		#region Methods

		#region Static Loaders

		private static Action<bool, Exception, Stream> DownloadHiddenCallback;

		/// <summary>
		/// Download the requested file(s) without showing the dialog window.
		/// </summary>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="callback">The action delegate to call once the download has completed.</param>
		/// <param name="fileNames">One or more Names or Ids or Keys of the files to be downloaded.</param>
		/// <returns>true if the download was started successfully; otherwise false.</returns>
		/// <remarks>
		/// <para>
		/// This method sets the <see cref="AutoCloseStream"/> property to false, so the caller will be responsible for closing the stream. Also the stream's position is set to zero
		/// so long as the stream is seekable.
		/// </para>
		/// </remarks>
		public static bool DownloadFilesHidden(string saveAsFileName, Stream stream, Action<bool, Exception, Stream> callback, params string[] fileNames)
		{
			return DownloadFilesHidden(DefaultDownloadUri, saveAsFileName, stream, callback, fileNames);
		}
		/// <summary>
		/// Download the requested file(s) without showing the dialog window.
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="callback">The action delegate to call once the download has completed.</param>
		/// <param name="fileNames">One or more Names or Ids or Keys of the files to be downloaded.</param>
		/// <returns>true if the download was started successfully; otherwise false.</returns>
		/// <remarks>
		/// <para>
		/// This method sets the <see cref="AutoCloseStream"/> property to false, so the caller will be responsible for closing the stream. Also the stream's position is set to zero
		/// so long as the stream is seekable.
		/// </para>
		/// </remarks>
		public static bool DownloadFilesHidden(string uri, string saveAsFileName, Stream stream, Action<bool, Exception, Stream> callback, params string[] fileNames)
		{
			return DownloadFilesHidden(uri, DefaultHandlerKey, saveAsFileName, stream, callback, fileNames);
		}
		/// <summary>
		/// Download the requested file(s) without showing the dialog window.
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="callback">The action delegate to call once the download has completed.</param>
		/// <param name="fileNames">One or more Names or Ids or Keys of the files to be downloaded.</param>
		/// <returns>true if the download was started successfully; otherwise false.</returns>
		/// <remarks>
		/// <para>
		/// This method sets the <see cref="AutoCloseStream"/> property to false, so the caller will be responsible for closing the stream. Also the stream's position is set to zero
		/// so long as the stream is seekable.
		/// </para>
		/// </remarks>
		public static bool DownloadFilesHidden(string uri, string handlerKey, string saveAsFileName, Stream stream, Action<bool, Exception, Stream> callback, params string[] fileNames)
		{
			DownloadHiddenCallback = callback;
			var dialog = new DownloadFileDialog(saveAsFileName, stream, true, fileNames);
			dialog.DownloadUri = uri;
			dialog.HandlerKey = handlerKey;
			dialog.AutoCloseStream = false;
			return dialog.BeginDownloadHidden();
		}

		/// <summary>
		/// Begins downloading the requested file to the specified Stream.
		/// </summary>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		/// <returns>A DownloadFileDialog instance.</returns>
		public static DownloadFileDialog DownloadFile(string saveAsFileName, Stream stream, string fileName)
		{
			var dialog = new DownloadFileDialog(saveAsFileName, stream, fileName);
			dialog.Show();
			return dialog;
		}
		/// <summary>
		/// Begins downloading the requested file to the specified Stream.
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		/// <returns>A DownloadFileDialog instance.</returns>
		public static DownloadFileDialog DownloadFile(string uri, string saveAsFileName, Stream stream, string fileName)
		{
			var dialog = new DownloadFileDialog(saveAsFileName, stream, fileName);
			dialog.DownloadUri = uri;
			dialog.Show();
			return dialog;
		}
		/// <summary>
		/// Begins downloading the requested files to the specified Stream.
		/// </summary>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="fileNames">One or more Names or Ids or Keys of the files to be downloaded.</param>
		/// <returns>A DownloadFileDialog instance.</returns>
		public static DownloadFileDialog DownloadFiles(string saveAsFileName, Stream stream, params string[] fileNames)
		{
			var dialog = new DownloadFileDialog(saveAsFileName, stream, fileNames);
			dialog.Show();
			return dialog;
		}
		/// <summary>
		/// Begins downloading the requested files to the specified Stream.
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="saveAsFileName">The name of the file the downloaded contents will be saved to.</param>
		/// <param name="stream">The stream to save the the downloaded contents to.</param>
		/// <param name="fileNames">One or more Names or Ids or Keys of the files to be downloaded.</param>
		/// <returns>A DownloadFileDialog instance.</returns>
		public static DownloadFileDialog DownloadFiles(string uri, string saveAsFileName, Stream stream, params string[] fileNames)
		{
			var dialog = new DownloadFileDialog(saveAsFileName, stream, fileNames);
			dialog.DownloadUri = uri;
			dialog.Show();
			return dialog;
		}
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileInline(string fileName)
		{
			OpenFileInline(DefaultDownloadUri, DefaultHandlerKey, fileName);
		}
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileInline(string uri, string fileName)
		{
			OpenFile(uri, DefaultHandlerKey, fileName, FileQueryCommand.OpenFileInline);
		}
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileInline(string uri, string handlerKey, string fileName)
		{
			OpenFile(uri, handlerKey, fileName, FileQueryCommand.OpenFileInline);
		}
		/// <summary>
		/// Begins downloading the requested file as an Attachment, directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileAsAttachment(string fileName)
		{
			OpenFileAsAttachment(DefaultDownloadUri, DefaultHandlerKey, fileName);
		}
		/// <summary>
		/// Begins downloading the requested file as an Attachment, directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileAsAttachment(string uri, string fileName)
		{
			OpenFile(uri, DefaultHandlerKey, fileName, FileQueryCommand.OpenFileAsAttachment);
		}
		/// <summary>
		/// Begins downloading the requested file as an Attachment, directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		public static void OpenFileAsAttachment(string uri, string handlerKey, string fileName)
		{
			OpenFile(uri, handlerKey, fileName, FileQueryCommand.OpenFileAsAttachment);
		}
		/// <summary>
		/// Begins downloading the requested file directly to the browser (Typical HTTP download behavior).
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the download http handler. For OOB must be the absolute Uri.</param>
		/// <param name="handlerKey">The Key that identifies the Handler that will process the download request.</param>
		/// <param name="fileName">The Name or Id or Key of the file to be download.</param>
		/// <param name="command">The FileQueryCommand.</param>
		static void OpenFile(string uri, string handlerKey, string fileName, FileQueryCommand command)
		{
			if (string.IsNullOrEmpty(fileName))
				throw new ArgumentNullException(fileName);
			var resolvedUri = FileQuery.ResolveUrl(uri);
			var query = FileQuery.CreateDownloadFileQuery(handlerKey, Guid.NewGuid(), fileName, command);
			var fullUri = new Uri(resolvedUri.ToString() + query);
			SafeNavigator.Navigate(fullUri, true);
		}
		#endregion

		static void GetDownloadUrl(string resourceRelativeUri, out Uri uri)
		{
			uri = null;
			if (string.IsNullOrEmpty(resourceRelativeUri))
				resourceRelativeUri = DownloadFileDialog.DefaultDownloadUri;
			uri = FileQuery.ResolveUrl(resourceRelativeUri);
		}

		bool BeginDownloadHidden()
		{
			try
			{
				GetDownloadUrl(this.DownloadUri, out resolvedUri);
				this.PostRequest();
				return true;
			}
			catch (Exception ex)
			{
				if (null != DownloadHiddenCallback)
				{
					try { DownloadHiddenCallback(false, ex, this.Stream); }
					catch { }
					DownloadHiddenCallback = null;
				}
			}
			return false;
		}

		void PostRequest()
		{
			var query = FileQuery.CreateDownloadFileQuery(this.HandlerKey, this.jobId, this.SaveAsFileName, FileQueryCommand.Prepare);
			var uri = new Uri(resolvedUri.ToString() + query, UriKind.Absolute);
			this.webClient = new WebClient();
			this.webClient.UploadStringCompleted += new UploadStringCompletedEventHandler(PostRequestCompleted);
			this.webClient.UploadStringAsync(uri, this.metadata);
		}
		void PostRequestCompleted(object sender, UploadStringCompletedEventArgs e)
		{
			this.webClient.UploadStringCompleted -= new UploadStringCompletedEventHandler(PostRequestCompleted);
			this.webClient = null;

			if (!e.Cancelled)
			{
				var error = e.Error;
				if (e.Result.Length > 0)
					error = new WebException(e.Result, e.Error);
				if (null != error)
				{
					#region Not Hidden
					if (!this.hidden)
					{
						this.ckbCloseWhenCompleted.IsEnabled = false;
						this.ckbCloseWhenCompleted.IsTabStop = false;
						this.btnClose.IsEnabled = false;

						MessageBox.Show(e.Error.Message);
						this.txtResult.Text = e.Error.Message;
					}
					#endregion
					this.DownloadCompleted(e.Cancelled, error);
					return;
				}
				this.GetRequest();
			}
			else
			{
				if (!this.hidden)
				{
					this.ckbCloseWhenCompleted.IsEnabled = false;
					this.ckbCloseWhenCompleted.IsTabStop = false;
					this.btnClose.IsEnabled = false;
				}
				this.DownloadCompleted(e.Cancelled, e.Error);
			}
		}

		void GetRequest()
		{
			var query = FileQuery.CreateDownloadFileQuery(this.HandlerKey, this.jobId, this.SaveAsFileName, mulitpleFiles ? FileQueryCommand.GetMultipleFiles : FileQueryCommand.GetSingleFile);
			var uri = new Uri(resolvedUri.ToString() + query);
			watch = Stopwatch.StartNew();
			this.webClient = new WebClient();
			this.webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(GetRequestProgress);
			this.webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(GetRequestCompleted);
			this.webClient.OpenReadAsync(uri);
		}
		void GetRequestProgress(object sender, DownloadProgressChangedEventArgs e)
		{
			var downloadFormat = "{0} in {1}";
			var downloadedSize = (string)new ByteConverter().Convert((double)e.BytesReceived, typeof(string), null, null);

			var elapsed = watch.Elapsed;
			var downloadedTime = (string)new TimeConverter().Convert(elapsed, typeof(string), null, null);

			this.Activity = string.Format(downloadFormat, downloadedSize, downloadedTime);
			this.TransferRate = e.BytesReceived / elapsed.TotalSeconds;
			this.Percentage = e.ProgressPercentage;
		}
		void GetRequestCompleted(object sender, OpenReadCompletedEventArgs e)
		{
			watch.Stop();
			watch = null;

			this.webClient.DownloadProgressChanged -= new DownloadProgressChangedEventHandler(GetRequestProgress);
			this.webClient.OpenReadCompleted -= new OpenReadCompletedEventHandler(GetRequestCompleted);

			if (!this.hidden)
			{
				this.ckbCloseWhenCompleted.IsEnabled = false;
				this.ckbCloseWhenCompleted.IsTabStop = false;
				this.btnClose.IsEnabled = false;
			}

			if (e.Cancelled)
			{
				this.DownloadCompleted(e.Cancelled, e.Error);
				return;
			}
			if (null != e.Error)
			{
				if (!this.hidden)
				{
					MessageBox.Show(e.Error.Message);
					this.txtResult.Text = e.Error.Message;
				}
				this.DownloadCompleted(e.Cancelled, e.Error);
				return;
			}

			try
			{
				if (e.Result.Length > 0)
				{
					var bytesRead = 0;
					var bufferLength = 0x1000; // 4KB
					var len = Convert.ToInt32(e.Result.Length);
					if (len < bufferLength)
						bufferLength = len;
					var buffer = new byte[bufferLength];
					while ((bytesRead = e.Result.Read(buffer, 0, buffer.Length)) != 0)
						this._stream.Write(buffer, 0, bytesRead);

				}
				this.DownloadCompleted();
			}
			catch (Exception ex)
			{
				if (!this.hidden)
					MessageBox.Show(ex.Message);
				this.DownloadCompleted(false, ex);
			}
		}

		void DownloadCompleted()
		{
			this.DownloadCompleted(false, null);
		}
		void DownloadCompleted(bool canceled, Exception error)
		{
			try
			{
				if (this.AutoCloseStream)
				{
					#region AutoCloseStream
					try
					{
						if (null != this._stream)
						{
							try { this._stream.Flush(); }
							catch { }
							try { this._stream.Close(); }
							catch { }
							this._stream = null;
						}
					}
					catch { }
					#endregion
				}
				else if (this.AutoResetPosition)
				{
					try
					{
						if (null != this._stream && this._stream.CanSeek)
							this._stream.Position = 0;
					}
					catch { }
				}

				this.isCanceled = canceled;
				this.isCompleted = true;

				if (!this.hidden)
				{
					#region Update UI
					try
					{
						if (this.isCanceled)
						{
							this.Title = "Download canceled";
							this.txtResult.Text = "Download canceled";
						}
						else
						{
							this.Title = "Download complete";
							this.txtResult.Text = "Download Complete";
						}

						this.txtResult.Visibility = Visibility.Visible;
						this.btnClose.Content = "Close";
						if (!this.ckbCloseWhenCompleted.IsChecked.Value == true)
							this.btnClose.IsEnabled = true;
						else
							this.Close();
					}
					catch { }
					#endregion
				}
			}
			finally
			{
				try { OnCompleted(canceled, error); }
				catch { }
				if (null != DownloadHiddenCallback)
				{
					try { DownloadHiddenCallback(canceled, error, this.Stream); }
					catch { }
					DownloadHiddenCallback = null;
				}
			}
		}
		void DownloadCanceled(Exception error)
		{
			this.isCanceled = true;
			if (!this.hidden)
				this.btnClose.IsEnabled = false;
			try { this.webClient.CancelAsync(); }
			catch { }
			this.DownloadCompleted(true, error);
		}

		#endregion

		#region Event Handlers
		void ckbCloseWhenCompleted_Unchecked(object sender, RoutedEventArgs e)
		{
			this.CloseWhenCompleted = false;
		}
		void ckbCloseWhenCompleted_Checked(object sender, RoutedEventArgs e)
		{
			this.CloseWhenCompleted = true;
		}
		void btnClose_Click(object sender, RoutedEventArgs e)
		{
			if (this.isCompleted)
				this.DialogResult = !this.isCanceled;
			else
				this.DownloadCanceled(null);
		}
		#endregion

		#region INotifyPropertyChanged Members
		/// <summary>
		/// Notifies clients that a property value has changed
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;
		/// <summary>
		/// Raises the UploadFileDialogViewModel.PropertyChanged event with the provided arguments
		/// </summary>
		/// <param name="property">The name of the Property that changed</param>
		private void OnPropertyChanged(string property)
		{
			if (!this.hidden)
			{
				var handler = this.PropertyChanged;
				if (null != handler)
					handler(this, new PropertyChangedEventArgs(property));
			}
		}
		#endregion
	}
	#endregion
}